package com.nulldev.util.io.zipAPI;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import com.nulldev.util.error.ErrorCallback;
import com.nulldev.util.io.FileUtil;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.io.MimeType;
import com.nulldev.util.io.lzma.LZMAInputStream;
import com.nulldev.util.io.lzma.lzma.Decoder;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.promise.Promiser;
import com.nulldev.util.promise.noresult.NoResultPromise;

public class ZIPFile {
	private static final Logger log = LoggerFactory.getLogger(true, true);
	
	private final File file;
	public ZIPFile(final File file) throws Exception {
		this.file = file;
		if (this.file == null || !this.file.exists() || this.file.isDirectory()) {
			throw new UnsupportedOperationException("Invalid file used!");
		}
		final String mime = FileUtil.getMimeType(file);
		log.debug("Mime: " + mime);
		if (!mime.equals("application/octet-stream") && !mime.equals("application/zip")) {
			throw new UnsupportedOperationException("File is not a ZIP file! [value=" + FileUtil.getMimeType(file) + "]");
		}
	}
	
	public void pdecompress(final File output, final ErrorCallback callback) {
		Promiser.promise(new NoResultPromise() {
			
			@Override
			public void run() throws Throwable {
				ZIPFile.this.decompress(output);
			}
			
			@Override
			public void error(Throwable throwable) {
				callback.onError(throwable);
			}
		});
	}
	
	/**
	 * Decompresses a zipped file.
	 * @param output [gzip = Expected Input is a File, zip = Expected Output is a Folder, lzma = Expected Input is a File]
	 * @throws Exception
	 */
	public void decompress(final File output) throws Exception {
		if (output == null) {
			throw new UnsupportedOperationException("decompress(output) -> output == null!");
		}
		if (output.exists() && !output.isDirectory()) {
			boolean o = output.delete();
			if (!o) {
				throw new UnsupportedOperationException("decompress(output) -> Cannot delete existing file!");
			}
			o = output.createNewFile();
			if (!o) {
				throw new UnsupportedOperationException("decompress(output) -> Cannot recreate file!");
			}
		}
		final String ext = FileUtil.getFileExtension(this.file);
		final FileInputStream fis = new FileInputStream(this.file);
		final FileOutputStream fos = (output.isDirectory() ? null : new FileOutputStream(output));
		if (ext.equals("gz")) {
			if (!output.exists()) {
				if (!output.createNewFile()) {
					throw new UnsupportedOperationException("decompress(output) -> File to extract not found!");
				}
			}else if (output.isDirectory()) {
				throw new UnsupportedOperationException("decompress(output) -> output cannot be a folder for GZIP decompression!");
			}
			if (fos == null) {
				throw new UnsupportedOperationException("decompress(output) -> fos == null!");
			}
			final GZIPInputStream gis = new GZIPInputStream(fis);
			
			byte[] buffer = new byte[8196];
			int len;
	        while ((len = gis.read(buffer)) > 0) {
	        	fos.write(buffer, 0, len);
	        }
	        
	        gis.close();
	        //gos.close();
		}else if (ext.equals("zip")) {
			if (!output.isDirectory()) {
				throw new UnsupportedOperationException("decompress(output) -> output is not a Folder!");
			}
			final ZipInputStream zis = new ZipInputStream(fis);
			//final ZipOutputStream zos = new ZipOutputStream(fos);
			ZipEntry ze = zis.getNextEntry();
			while (ze != null) {
				final String fileName = ze.getName();
				final File o = FileUtil.safeCreate(output + File.separator + fileName);
				if (!o.exists()) {
					o.createNewFile();
				}
				final FileOutputStream zfos = new FileOutputStream(o);   
				
				byte[] buffer = new byte[8196];
				int len;
		        while ((len = zis.read(buffer)) > 0) {
		        	zfos.write(buffer, 0, len);
		        }
		        
		        zfos.close();
		        ze = zis.getNextEntry();
			}
			
			//byte[] buffer = new byte[8196];
			//int len;
	        //while ((len = zis.read(buffer)) > 0) {
	        //	zos.write(buffer, 0, len);
	        //}
			
			zis.closeEntry();
			zis.close();
	        // zos.close();
		}else if (ext.equals("lzma")) {
			if (!output.exists()) {
				if (!output.createNewFile()) {
					throw new UnsupportedOperationException("decompress(output) -> File to extract not found!");
				}
			}else if (output.isDirectory()) {
				throw new UnsupportedOperationException("decompress(output) -> output cannot be a folder for LZMA decompression!");
			}
			if (fos == null) {
				throw new UnsupportedOperationException("decompress(output) -> fos == null!");
			}
			final BufferedInputStream bis = new BufferedInputStream(fis);
			final LZMAInputStream inputStream = new LZMAInputStream(
                    bis,
                    new Decoder());
			final BufferedOutputStream bos = new BufferedOutputStream(fos);
			IOUtils.copy(inputStream, bos);
			bis.close();
			inputStream.close();
			bos.close();
		}else {
			throw new UnsupportedOperationException("Unsupported compressed file format! [fileExt=" + ext + "]");
		}
		fis.close();
		if (fos != null) {
			fos.close();
		}
	}
}
